home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2010 April
/
PCWorld0410.iso
/
pluginy Firefox
/
1833
/
1833.xpi
/
modules
/
yoonoBkmSync.js
next >
Wrap
Text File
|
2009-12-16
|
39KB
|
1,446 lines
var EXPORTED_SYMBOLS = ["YOONO_BKM"];
Components.utils.import("resource://yoono/yoonoPrefs.js");
var yoono = {};
var log = {info:function() {},debug:function() {},warn:function(){},error:function (){},fatal:function(){}};
// Globals const
var CI = Components.interfaces;
var CL = Components.classes;
const OBS=CL['@mozilla.org/observer-service;1'].getService(CI.nsIObserverService);
const DIRSERVICE = CL['@mozilla.org/file/directory_service;1'].getService(CI.nsIProperties);
const MAX_PATH_LENGTH = 5000;
const LIVETAG = '.LIV';
const TOOLBARNAME = "PERSONAL_BOOKMARK_FOLDER";
const PRIVATEFILE = "yoonoprivatefolders.txt";
const YOONO_DIR = "yoono";
const BACKUP_DIR = "yoonobookmarkbackups";
const HISTORYPREF = "extensions.yoono.bookmarks.maxhistory";
const BACKUPS_NUMBER = 5;
const PREFS = CL['@mozilla.org/preferences-service;1'].getService(CI.nsIPrefBranch);
/*** Globals var ***/
var gCurrentTree = null;
var gCurrentNode = null;
var nbBkms = 1;
var gBkmById = {};
var gBkmByUrl = {};
/***************************************************/
/*** Browser specific version Javascript loading ***/
var restore={};
try {
// Load version specific JS from : chrome/$ff-version$/*
var loader = CL["@mozilla.org/moz/jssubscript-loader;1"].createInstance(CI.mozIJSSubScriptLoader);
loader.loadSubScript("chrome://yoonospecific/content/bookmarks.js");
loader.loadSubScript("chrome://yoonospecific/content/bookmarks-restore.js",restore);
} catch(e) {
var console = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
var scriptError = CL["@mozilla.org/scripterror;1"].createInstance(CI.nsIScriptError);
scriptError.init("Error loading specific code >>> "+e+" <<<\n"+e.stack, e.filename, null, e.lineNumber, null, scriptError.errorFlag, "");
console.logMessage(scriptError);
}
function Bookmarks() {
this.wrappedJSObject=this;
}
/********************************/
/*** Component initialization ***/
Bookmarks.prototype.start = function (y) {
try {
yoono=y;
log=y.log;
log.debug("load bookmarks component");
function launch() {
privateMgr.init();
// User already registered and not anonymous
if (YOONO_PREFS.get('userid')) {
var aIsModified = YOONO_PREFS.storeLastModified("init");
var aSyncMode = ( aIsModified == true ? "manual-sync" : "auto-sync" );
if (YOONO_PREFS.get('nosynchro')) aSyncMode = 'no-sync';
if(-1 == YOONO_PREFS.get('userid').indexOf(':')) {
aSyncMode = 'no-sync';
}
yoono.server.launch('connect',aSyncMode); // on se connecte apres avoir charge les marque-pages
}
// Disable backup on each firefox startup
//yoono.bkm.backup();
}
this.initBackups();
// Launch version specific loading
start(launch);
} catch(e) {
log.exception(e);
this.dump();
}
}
Bookmarks.prototype.uninstall = function () {
privateMgr.uninstall();
}
/*
Bookmarks.prototype.mergeBookmarksWithServer = function() {
YOONO_PREFS.set('synchroaction', "merge");
// The load-all-links command is always processed last, whatever its position in the request
yoono.server.launch('load-all-links');
}
Bookmarks.prototype.importBookmarksFromServer = function() {
YOONO_PREFS.set('synchroaction', "import");
// The load-all-links command is always processed last, whatever its position in the request
yoono.server.launch('load-all-links');
}
Bookmarks.prototype.exportBookmarksOnServer = function() {
yoono.server.launch('save-all-links');
}
*/
/***********************/
/*** Backups service ***/
Bookmarks.prototype.initBackups = function () {
// Search backup dir
this.backupDir = DIRSERVICE.get("ProfD",CI.nsIFile);
this.backupDir.append(BACKUP_DIR);
if(!this.backupDir.exists())
this.backupDir.create(CI.nsIFile.DIRECTORY_TYPE, 0755);
}
Bookmarks.prototype.setMaxBackups = function (n) {
PREFS.setIntPref(HISTORYPREF,n);
this.flushBackups();
}
Bookmarks.prototype.getMaxBackups = function () {
return PREFS.getIntPref(HISTORYPREF);
}
Bookmarks.prototype.flushBackups = function () {
// Remove one file, if we reach backups limit
while(true) {
var e = this.backupDir.directoryEntries;
var older=null;
var nbFiles=0;
while(e.hasMoreElements()) {
var file=e.getNext().QueryInterface(Components.interfaces.nsIFile);
if (!file.isFile()) continue;
if (!older || file.lastModifiedTime < older.lastModifiedTime) {
older=file;
}
nbFiles++;
}
if (nbFiles>this.getMaxBackups())
older.remove(false);
else
break;
}
}
Bookmarks.prototype.backup = function () {
try {
// Create a new backup file
var d=new Date();
var file = this.backupDir.clone();
file.append(d.getFullYear()+"_"+(d.getMonth()+1)+"_"+d.getDate()+"-"+d.getHours()+"h"+d.getMinutes()+"m"+d.getSeconds()+"s.html");
// Build backup!
restore.backup(file);
this.flushBackups();
} catch(e) {
log.exception(e);
}
}
Bookmarks.prototype.restore = function (name) {
// Disable server synch
Server2Browser.working=true;
// Launch restore
log.debug("START RESTORE");
var file=this.backupDir.clone();
file.append(name);
restore.restore(file);
log.debug("END RESTORE");
// Export all bkms
yoono.server.launch('save-all-links');
// Reenable server synch
Server2Browser.working=false;
}
Bookmarks.prototype.getBackupList = function (file) {
var e = this.backupDir.directoryEntries;
var backups=[]
while(e.hasMoreElements()) {
var file=e.getNext().QueryInterface(Components.interfaces.nsIFile);
if (!file.isFile()) continue;
backups.push(file);
}
backups.sort(function (a,b) {return a.lastModifiedTime - b.lastModifiedTime;});
var list=[];
for(var i=0;i<backups.length;i++) {
var file=backups[i];
var m=file.leafName.match(/(\d+)_(\d+)_(\d+)-(\d+)h(\d+)m(\d+)s\.html/);
if (m) {
list.push({file:file, name:file.leafName, date:new Date(m[1],parseInt(m[2]) - 1,m[3],m[4],m[5],m[6])});
} else
file.remove(false);
}
return list;
}
/*** Debuging ***/
Bookmarks.prototype.dump = function () {
log.info("Bookmark tree : \n"+gCurrentTree.toString());
}
Bookmarks.prototype.bookmarksNumber = function () {
var c=0;
for each(var n in gBkmById) {
if (!n.isFolder()) {
c++;
}
}
return c;
}
/************************/
/*** Common interface ***/
Bookmarks.prototype.loadAllLinks = function (links) {
try {
return Server2Browser.loadAllLinks(links);
} catch(e) {
log.exception(e);
}
}
Bookmarks.prototype.getSyncId = function () {
return gCurrentTree?gCurrentTree.getSyncid():"";
}
Bookmarks.prototype.getCurrentTree = function () {
return gCurrentTree;
}
Bookmarks.prototype.getNbBkms = function () {
return nbBkms;
}
Bookmarks.prototype.isPrivate = function (id) {
if (gBkmById[id])
return gBkmById[id].isPrivate();
return false;
}
Bookmarks.prototype.addToPrivateList = function (id) {
privateMgr.addToList(id);
}
Bookmarks.prototype.removeFromPrivateList = function (id) {
privateMgr.removeFromList(id);
}
Bookmarks.prototype.getNode = function (id) {
return gBkmById[id];
}
/**************************/
/*** Specific interface ***/
// TODO : check utility of each function
// Returns full boookmark list as an array
Bookmarks.prototype.getFullListUrl = function () {
var urlList = [];
for each (var node in gBkmById) {
if (node.isFolder() || !node.getUrl())
continue;
urlList.push(node.getUrl());
}
return urlList;
}
// Returns array of url contained in the folder whose id is passed
Bookmarks.prototype.getNodeListUrl = function(nodeId) {
var node = this.getNode(nodeId);
if (node)
return(this.getUrlsInSubFolder(node));
else // we may select places top container "all bookmarks" which are not tracked by this component
return this.getFullListUrl();
}
Bookmarks.prototype.getUrlsInSubFolder = function(node) {
var obj = {
list : [],
onNode : function (node) {
if(!node.isFolder()) {
this.list.push(node.getUrl());
}
}
}
node.traverseNode(obj, 'force');
return obj.list;
}
Bookmarks.prototype.isKnownUrl = function (url) {
if (gBkmByUrl[url])
return true;
else
return false;
}
Bookmarks.prototype.addBkmAtRoot = function (url, title) {
gBrowserBkm.createBookmark(title, url, gCurrentTree);
}
/******************************/
/*** Old code compatibility ***/
// TODO : remove all call
Bookmarks.prototype.getNodesForTreeView = function (node, level) {
log.backtrace("Deprecated call!");
// initialisation
if (!node)
var node = gCurrentTree;
if (!node)
return {open:false, name:'none', children: []};
if (!level)
var level = 0;
var obj = new Object();
obj.open = false;
obj.level = level;
obj.node = node;
obj.published = false;
obj.origPublished = false; // to save original state to detect changes
obj.private = node.isPrivate();
obj.parent = null;
obj.origPrivate = node.isPrivate(); // to save original state to detect changes
obj.id = BKMSERV.bookmarksMenuFolder;
obj.children = [];
return obj;
}
// END of public interface! //
//////////////////////////////
/*
// TODO : make BkmsObserver specific to FF3
Browser2Server = {
notifyServer : function( cmd, arg ) {
if (Server2Browser.isWorking()) return;
yoono.server.addToNextCommandScript(command, arg);
},
itemAdded : function (node) {
if (node.isAcceptable() && !node.isFolder())
this.notifyServer('add-link', node);
},
itemRemoved : function (node) {
if (node.isAcceptable())
this.notifyServer('remove-link', node);
},
itemChanged : function(oldNode, newNode) (
if (newNode.update() && newNode.isAcceptable())
this.notifyServer('update-link', [oldNode, newNode]);
},
itemMoved : function(node) {
}
};
*/
////////////////////////////
// BkmsObserver
// :: observe bookmarks modifications
var BkmsObserver = {
_working : false,
onBeginUpdateBatch: function() {
log.debug("Begin update batch");
this._working = true;
},
onEndUpdateBatch: function() {
log.debug("End update Batch");
this._working = false;
},
notifyServer : function(command, node) {
if (Server2Browser.isWorking()) return;
if (this._working) {
yoono.server.addToNextCommandScript(command, node);
} else {
yoono.server.launch(command, node);
}
},
onItemAdded: function(id, folder, index) {
//log.debug("add item id : "+id);
var parent=gBkmById[folder];
if (!parent) {
//log.debug("Ignore bookmarks with parent not in our tree");
return ;
}
if (parent.isLive()) {
//log.debug("we ignore subitem of live bookmarks");
return;
}
if (gBkmById[id]) {
var node=gBkmById[id];
} else {
var node=new bkmNode(id, parent);
parent.addChild(node);
}
//log.debug("parent : "+parent.getName()+" name:"+node.getName()+" isFolder:"+node.isFolder());
// Delay creation, because livemarks are really livemarks some times later :/ (ie LIVESERV.isLivemark()->true) [FF3]
// (they are first folder, then livemark)
// (they are valid livemarks when we receive itemChanged on feedURI property)
if (!Server2Browser.isWorking()) {
var _this=this;
var callback = {
notify : function(timer) {
try {
if (node.isAcceptable() && !node.isFolder()) // we must delay isAcceptable because we check isFolder into...
_this.notifyServer('add-link', node);
} catch(e) {
log.exception(e);
}
}
};
var timer = CL['@mozilla.org/timer;1'].createInstance(CI.nsITimer);
timer.initWithCallback(callback, 1000 , timer.TYPE_ONE_SHOT);
}
return node;
},
onItemRemoved: function(id, folder, index) {
try {
//log.debug("remove item : "+id);
var node=gBkmById[id];
if (!node) {
//return log.debug("removed node not tracked");
return;
}
if (node.isFolder()) {
var children = node.getAllChildren();
for each(var child in children) {
child.remove();
if (child.isAcceptable())
this.notifyServer('remove-link', child);
}
} else {
if (node.isAcceptable())
this.notifyServer('remove-link', node);
}
node.remove();
} catch(e) {
log.exception(e);
yoono.bkm.dump();
}
},
onItemMoved: function(id, oldParent, oldIndex, newParent, newIndex) {
try {
if (oldParent == newParent)
return log.debug("order moves are ignored");
log.debug("move item : "+id+" parentId:"+newParent);
var node=gBkmById[id];
// For unknown bookmark, check if destination is known
if (!node) {
if (gBkmById[newParent]) {
return this.onItemAdded(id, newParent, newIndex);
} else {
return log.debug("moved node not tracked (may be in private)");
}
}
log.debug("oldparent:"+oldParent+" > newparent:"+newParent);
if (node.isFolder()) {
// First save a copy of old children
var childs=[];
node.traverseNode({onNode:function(child){
if (!child.isFolder())
childs.push(child.clone());
}});
// Update originals
node.getParent().removeChild(node);
var dest=gBkmById[newParent];
dest.addChild(node);
node.update(dest);
// Send requests
for each(var child in childs) {
var newChild=gBkmById[child.getId()];
log.debug("oldpath:"+child.getPath().toServer+" > newpath: "+newChild.getPath().toServer);
if (newChild.isAcceptable())
this.notifyServer('update-link', [child, newChild]);
}
} else {
var oldNode=node.clone();
node.getParent().removeChild(node);
var dest=gBkmById[newParent];
dest.addChild(node);
node.update(dest);
log.debug("oldpath:"+oldNode.getPath().toServer+" > newpath: "+node.getPath().toServer);
if (node.isAcceptable())
this.notifyServer('update-link', [oldNode, node]);
}
} catch(e) {
log.exception(e);
yoono.bkm.dump();
}
},
onItemChanged: function(id, property, isAnnotationProperty, value) {
try {
if ( ['title', 'uri', 'livemark/feedURI'].indexOf(property) == -1 ) {
//log.info("only title, uri and feedURI properties are tracked (not "+property+")"+id+" > "+(gBkmById[id]?gBkmById[id].isFolder():"/"));
return;
}
//log.debug("change item : "+id+"/"+property+"/"+isAnnotationProperty+"/"+value);
var node=gBkmById[id];
//////
if (node.isFolder() && property=="title") {
// First save a copy of old children
var childs=[];
node.traverseNode({onNode:function(child){
if (!child.isFolder())
childs.push(child.clone());
}});
// Update originals
node.update();
// Send requests
for each(var child in childs) {
var newChild=gBkmById[child.getId()];
log.debug("oldpath:"+child.getPath().toServer+" > newpath: "+newChild.getPath().toServer);
if (newChild.isAcceptable())
this.notifyServer('update-link', [child, newChild]);
}
} else {
var oldNode=node.clone();
if (node.update() && node.isAcceptable()) {
// See onItemAdded ...
if (property=="livemark/feedURI" && !oldNode.getUrl())
return log.debug("we ignore first livemark url notification");
this.notifyServer('update-link', [oldNode, node]);
} else
log.error("not acceptable ? no update ? ");
}
} catch(e) {
log.exception(e);
yoono.bkm.dump();
}
},
onItemVisited: function(id, visitID, time) {
},
QueryInterface: function(iid) {
if (iid.equals(Ci.nsINavBookmarkObserver) ||
iid.equals(Ci.nsISupports)) {
return this;
}
throw Components.results.NS_ERROR_NO_INTERFACE;
}
};
//////////////////////////////
//////////////////////////////
// Server2Browser
// :: handle info from server and sync with rdf view
// :: initial loading of all bookmarks from server
var Server2Browser = {
isWorking : function() {return this.working;},
// la liste des marque-pages pr�sents dans l'ordi
notOnServer : {},
// une liste des chemins deja calcules
foldersList : {},
// list of incoming private folders
privateIncomingFoldersList : {},
// List of folders that will have to be added to private folder file
newPrivateFolders : {},
// List of links that where in a browser private folder that the synchro sets public
linksBecomingPublic : {},
/*
* List all private folders marked as such in incoming folder list
*/
listPrivateIncomingFolders : function (linkList) {
var key = '';
for each (var addLink in linkList) {
var privateFolder = addLink.@['private'].toString();
if('CLIENT' == privateFolder) {
key = addLink.@path.toString();
log.debug('Private incoming folder found : ' + key);
this.privateIncomingFoldersList[key] = {exists: false} ;
}
}
},
/*
* manages the list of folders that where in a browser private folder that the synchro
* now sets to public
* @param node: folder that becomes public
*/
addLinksBecomingPublic : function(node) {
var add = {
onNode : function() {}
};
add.onNode = function(node) {
if (node.isFolder() )
return;
var key = node.getPath().toServer + node.getUrl();
// log.debug("XXX BECOMING PUBLIC : " + key);
Server2Browser.linksBecomingPublic[key] = {
'node' : node,
'mustCreate' : false
};
};
node.traverseNode(add);
},
/*
* @param linkList : liste des elements a ajouter a l'arborescence (object xmlList)
*/
loadAllLinks : function (linkList) {
log.debug('begin loadAllLinks');
this.foldersList = {};
this.privateIncomingFoldersList = {};
this.newPrivateFolders = {};
this.linksBecomingPublic = {};
this.notOnServer = {};
this.working = true;
var _this=this;
gBrowserBkm.runInBatchMode(function () {
_this.loadAllInBatched(linkList);
});
return this.resp;
},
loadAllInBatched : function(linkList) {
// on renvoie l'action effectivement effectu�e, et le nombre de marque-pages import�s
this.resp = {
action : YOONO_PREFS.get('synchroaction'),
// nombre reellement crees
added : 0,
// importes depuis le serveur
imported : 0,
clearCommands : true
};
// List can contain (empty) incoming private folders just to be able not to erase them from the browser
// in case they are not marked private yet (sync...)
// They have the private="CLIENT" attribute. Parse the list to find them and set them as private
this.listPrivateIncomingFolders(linkList);
// on pousse tous les noeuds existants dans notOnServer pour detecter les suppressions a faire
// (except private folders, either existing or incoming)
var aNotOnServerCount = 0;
for each (var node in gBkmById) {
if (node.isFolder() ) {
var path = node.getPath().toServer;
// If private folder, not in the incoming private folders list, set it to public
if(!this.privateIncomingFoldersList[path] && node.isPrivate()) {
log.debug('Path ' + path + ' is now public on server');
var id = node.getId();
// Remove it from list of private folders
privateMgr.removeFolderFromList(id);
node.setPrivate(false);
// This folder may have content that is not yet on the server (added when it was private)
// and that must be added, excluding what is already on the server... what a mess :-)
// Store its content in a special list that will be used with the notOnServer list to build
// the add-link orders (and to leave them on the computer !)
this.addLinksBecomingPublic(node);
}
}
// skip private nodes, and other stuff
if (!node.isAcceptable()) {
continue;
}
if (node.isFolder() ) {
this.foldersList[node.getPath().toServer] = node;
} else {
var key = node.getPath().toServer + node.getUrl();
if (!this.notOnServer[key]) {
this.notOnServer[key] = new Array(); // array to manage duplicates
}
this.notOnServer[key].push(node);
aNotOnServerCount++;
}
}
// Send a global notifications that we are starting a synchro
OBS.notifyObservers (this,"yoono-synchro-start",YOONO_PREFS.get('synchroaction'));
yoono.bkm.backup();
log.debug('notOnServer.length : '+aNotOnServerCount);
this.saveLinks(linkList);
log.debug('imported='+this.resp.imported+', added='+this.resp.added+', synchroaction='+YOONO_PREFS.get('synchroaction'));
if (YOONO_PREFS.get('synchroaction') == 'import') {
if(this.resp.imported > 0) {
var length = 0;
// Must delete from server links that remain in notOnServer
for each (var nodeList in this.notOnServer) {
length += nodeList.length;
}
log.debug('length='+length);
if (length) {
for (var i in this.notOnServer) {
log.debug('i='+i);
// Remove bkms that were not on server
// except if they used to be in a private folder that became public during this sync
if(!this.linksBecomingPublic[i]) {
for (var j = this.notOnServer[i].length ; j-- > 0; ) {
this.removeBkm(this.notOnServer[i][j]);
}
} else {
// Mark link for addition to server
this.linksBecomingPublic[i].mustCreate = true;
}
delete (this.notOnServer[i]);
}
}
// Create the links that were added to private folders that became public because of the sync
log.debug('Adding links that became public :');
var cpt = 0;
for (var newNode in this.linksBecomingPublic) {
if(this.linksBecomingPublic[newNode].mustCreate) {
log.debug('Adding ' + newNode);
yoono.server.launch('add-link', this.linksBecomingPublic[newNode].node);
}
}
log.debug(cpt + ' links that became public added.');
} else {
log.warn("abnormal situation : invalid import, server returns 0 link. Local bookmarks aren't modified");
this.resp.clearCommands = false;
}
}
// Add to private folder list file the new ones just loaded
privateMgr.appendToList(this.newPrivateFolders);
aNotOnServerCount=0;
for(var key in this.notOnServer)
aNotOnServerCount++;
if (aNotOnServerCount>0)
yoono.server.launch('save-all-links-add', this.notOnServer);
this.working = false;
this.notOnServer = {};
this.foldersList = {};
// Send a global notifications that we are ending a synchro
// Needed by sidebar Wizard to know when to send bkm sharing information
OBS.notifyObservers (this,"yoono-synchro-end",YOONO_PREFS.get('synchroaction'));
log.debug('end loadAllLinks');
return this.resp;
},
/*
@param linkList : liste d'element de la forme <link title="aTitle" url="aUrl"/>
cree les liens correspondants
*/
saveLinks : function(linkList) {
for each (var addLink in linkList) {
try {
var pathAsStr = addLink.@path.toString();
var url = addLink.@url.toString();
var title = addLink.@title.toString();
var pos = title.lastIndexOf(LIVETAG);
if (pos != -1 && pos == title.length - LIVETAG.length) {
title = title.slice(0, -LIVETAG.length);
var type = 'l';
} else {
var type = 'b';
}
if (title.match(/^NOTITLE-/))
title = '';
var key = pathAsStr + url;
if (this.notOnServer[key]) {
// on prend le premier
var node = this.notOnServer[key][0];
// si on recoit une url deja presente avec un titre different,
// on ecrase le titre existant au lieu de dupliquer le mp
if (node.getName() != title) {
gBrowserBkm.rename(node,title);
}
// on supprime les doublons
for (var i = this.notOnServer[key].length ; i-- > 1; ) {
this.removeBkm(this.notOnServer[key][i]);
}
// Since the bkm exists on the server, remove it from the list
delete (this.notOnServer[key]);
} else {
// new or modified bkm
this.checkOrCreateBkm(addLink, pathAsStr, title, url, type);
}
this.resp.imported++;
} catch(e) {
log.exception(e);
}
}
},
checkOrCreateBkm : function(addLink, pathAsStr, title, url, type) {
var folder;
var privateFolder = addLink.@['private'].toString();
var path = yoono.utils.splitPath(pathAsStr);
if ('/' != pathAsStr) {
if(!(folder = this.foldersList[pathAsStr])) {
folder = this.getOrCreateFolder(path);
this.foldersList[pathAsStr] = folder;
}
} else {
folder = gCurrentTree;
}
// If private folder received from server already exists as not private
if(("CLIENT" == privateFolder) && Server2Browser.privateIncomingFoldersList[pathAsStr] && Server2Browser.privateIncomingFoldersList[pathAsStr].exists) {
// Record it as 'to be added to private folders'
var id = folder.getId();
if('/' == pathAsStr) id = gCurrentTree.id;
log.debug('New private folder: Path:' + pathAsStr + ' id: ' + id + ' title: ' + title + ' url: ' + url);
Server2Browser.newPrivateFolders[id] = true;
// delete it from list so that later 'isAcceptable' calls won't be disturbed...
// setting it public again would be impossible otherwise
delete (Server2Browser.privateIncomingFoldersList[pathAsStr]);
}
//log.info('Checking Path:' + pathAsStr + ' title: ' + title + ' url: <' + url + '> folder : \n'+folder.getName());
if (!url)
return false;
for each(var c in folder.getChildren()) {
if (c.getUrl() == url) {
return false;
}
}
if (type == 'b') {
gBrowserBkm.createBookmark( title, url, folder);
} else if (type == 'l') {
gBrowserBkm.createLivemark( title, url, folder);
}
this.resp.added++;
return true;
},
getOrCreateFolder : function(path) {
if (path.length == 1) {
return gCurrentTree;
}
if (path[(path.length - 1)] == TOOLBARNAME && path.length == 2) {
return gToolbarNode;
}
var parent = this.getOrCreateFolder(path.slice(0, -1));
var lastFolderName = path[path.length - 1];
for each(var c in parent.getChildren()) {
if (c.getName() == lastFolderName && c.isFolder()) {
return c;
}
}
return gBrowserBkm.createFolder(lastFolderName, parent);
},
removeBkm : function (node) {
//log.debug('remove node (name='+node.getName()+',path='+node.getPath().toServer + ')');
var parent = node.getParent();
gBrowserBkm.remove(node);
if (parent.isFolder() && parent.getChildren().length==0)
gBrowserBkm.remove(parent);
},
}
///////////////////////////////
///////////////////////////////
// bkmNode
// :: each bookmark or folder are mapped to one bkmNode
// :: generate Unique ID
// Public common interface
bkmNode.prototype.getId = function () {
return this.id;
}
bkmNode.prototype.getUrl = function() {
return this.url;
}
bkmNode.prototype.getParent = function () {
return this.parent;
}
bkmNode.prototype.getName = function () {
return this.name;
}
bkmNode.prototype.getChildren = function () {
return this.childsList;
}
bkmNode.prototype.isPrivate = function () {
return this.private;
}
bkmNode.prototype.isChildOfPrivate = function () {
return this.childOfPrivate;
}
// ""Protected"" interface (used only by bookmark service)
bkmNode.prototype.setPrivate = function (newStatus) {
if (newStatus!=this.private) {
this.private=newStatus;
if(this.private) {
nbBkms --;
} else {
nbBkms ++;
}
this.resetSyncid();
function traverse(node) {
for each(var child in node.childsList) {
if (!child.isPrivate()) {
child.childOfPrivate=newStatus;
traverse(child);
}
}
}
traverse(this);
}
}
bkmNode.prototype.addChild = function (child) {
nbBkms ++ ;
this.childsList.push(child);
this.resetSyncid();
}
bkmNode.prototype.clone = function () {
var clone={};
for(var i in this) {
clone[i]=this[i];
}
return clone;
}
bkmNode.prototype.remove = function () {
this.parent.removeChild(this);
delete gBkmById[this.id];
nbBkms --;
if (this.isBookmark() || this.isLive())
delete gBkmByUrl[this.getUrl()];
if (this.isFolder()) {
for each(c in this.childsList)
c.remove();
}
delete this.childsList;
}
bkmNode.prototype.toString = function (indent) {
if (!indent)
indent="";
if (this.isFolder()) {
var str=indent+"[ #"+this.id+" sync:"+this.syncid+" t:"+this.type+", "+(this.private?"private":"public")+" , name:"+this.getName()+"\n";
for(var i in this.childsList) {
str+=this.childsList[i].toString(indent+" ");
}
str+=indent+"]\n";
return str;
} else {
var name=this.getName();
return indent+"( #"+this.id+" sync:"+this.syncid+" t:"+this.type+" , name:"+(name?name.substr(0,50):"")+" )\n";
}
}
bkmNode.prototype.getAllChildren = function () {
if (!this.isFolder())
return [];
var children = this.childsList;
for(var i in this.childsList) {
children=children.concat(this.childsList[i].getAllChildren());
}
return children;
}
/*
* @ return : un objet path comprenant :
* .toSync : une chaine qui sera utilisee pour calculer le sync-id
* .toServer : une chaine qui sera envoyee au serveur
*/
bkmNode.prototype.getPath = function() {
if (!this.path) {
if (!this.parent) {
// fin de la recursion
this.path = {
toSync : '/',
toServer : '/',
};
} else {
var pPath=this.parent.getPath();
this.path = {toSync:pPath.toSync, toServer:pPath.toServer};
if (this.isFolder()) {
if (this.parent.parent) { // If the parent is not the root node
this.path.toSync += '/';
this.path.toServer += '/';
}
this.path.toSync += yoono.utils.normalize(this.getName()).toLowerCase();
this.path.toServer += yoono.utils.escapePath(this.getName());
}
}
}
return this.path;
}
bkmNode.prototype.getSyncid = function () {
//log.debug(" get syncid : "+this.id+" "+this.syncid+" "+this.name);
if (!this.syncid) {
var aPath = this.getPath().toSync;
if (this.isFolder()) {
var str = aPath + 'F';
var sum = this.stringToSum(aPath);
var urlExist = {};
for (var i = this.childsList.length; i--> 0;) {
var item= this.childsList[i];
var url = item.getUrl();
if (item.isAcceptable() && !urlExist[url]) {
sum += item.getSyncid();
if (url) {
urlExist[url] = true;
}
}
}
} else {
var aName = this.getName().substring(0,124);
var aHref = this.getUrl();
aName = yoono.utils.normalize(aName);
if( aHref ) { // patch pour Firefox 2.0 (microsummaries url=null)
var aUrl = aHref.substring(0,255);
var str = aPath.concat( aName.toLowerCase() , aUrl , 'B');
//var str=[aPath,aName.toLowerCase(),aUrl,'B'].join("");
sum = this.stringToSum(str);
}
}
this.syncid = sum;
}
return this.syncid;
}
var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
// we use UTF-8 here, you can choose other encodings.
converter.charset = "UTF-8";
// Inspiration : http://www.webtoolkit.info/javascript-utf8.html
bkmNode.prototype.stringToSum = function (str) {
var a = 1;
var b = 0;
var l = 0;
var result = {};
var data = converter.convertToByteArray(str, result);
l=result.value;
for (var i = 0/*, j=0*/ ; i < l; /*j++*/) {
// Convert from unicode array to "charCodeAt" values
var code = -1;
if (data[i]<128) {
code=data[i];
i++;
} else if ((data[i] > 191) && (data[i] < 224)) {
code=(((data[i] & 31) << 6) | (data[i+1] & 63));
i+=2;
} else {
code=((data[i] & 15) << 12) | ((data[i+1] & 63) << 6) | (data[i+2] & 63);
i+=3;
}
//if (code!=str.charCodeAt(j))
// log.debug(this.id+"@"+i+" : "+str[i]+" -> "+code+"/"+str.charCodeAt(j)+" ["+str.length+"/"+l+"]");
a += code;
b += a + code;
}
return (a % 65521) + 65536 * (b % 65521);
}
/*
bkmNode.prototype.stringToSum= function(buf)
{
var adler = 1;
var len = buf.length;
var NMAX = 3854;
var BASE = 65521;
var s1 = adler & 0xffff;
var s2 = (adler & 0xffff0000) / 65536;
var k;
var bpos = 0;
while (len > 0) {
k = len < NMAX ? len : NMAX;
len -= k;
while (k > 0) {
s1 = (s1 + buf.charCodeAt(bpos)) & 0xffffffff;
s2 = (s2 + s1) & 0xffffffff;
bpos += 1;
k -= 1;
}
s1 = s1 % BASE;
s2 = s2 % BASE;
}
return 2;
return ((s2 & 0x0000ffff)*65536)+s1;
}
*/
bkmNode.prototype.removeChild = function(node) {
if (!this.isFolder() || !this.childsList)
return;
for(i=0;i<this.childsList.length;i++){
if(node==this.childsList[i]) {
this.childsList.splice(i, 1);
nbBkms --;
break;
}
}
this.resetSyncid();
}
bkmNode.prototype.resetSyncid = function () {
var pNode = this;
while (pNode) {
pNode.syncid = 0;
pNode = pNode.getParent();
}
}
bkmNode.prototype.isAcceptable = function () {
if (this.getPath().toServer.length > MAX_PATH_LENGTH)
return false;
if (this.isChildOfPrivate())
return false;
return true;
/*
// if folder, check if it exists in incoming private folder list so that it won't
// be erased even if wasn't private before import
if (this.isFolder()) {
var key = this.getPath().toServer;
if(Server2Browser.privateIncomingFoldersList[key] && !this.private) {
log.debug('Found existing public folder ' + key);
Server2Browser.privateIncomingFoldersList[key].exists = true;
this.private = true;
return false;
}
}
*/
}
/*
* traverse recursivement le noeud
* l'objet peut contenir une fonction onNode
* a etre appliquee a chaque noeud
* par d�faut, on n'applique pas cette m�thode
* � un noeud non acceptable, mais on peut forcer
*/
bkmNode.prototype.traverseNode = function (obj, force) {
if (!this.isAcceptable() && !force)
return;
obj.onNode(this);
if (this.isFolder()) {
for (var i = this.childsList.length ; i-- > 0; ) {
this.childsList[i].traverseNode(obj, force);
}
}
}
/*
@ param node : un noeud de l'arborescence
@ param xml : un objet xml
@ return : l'objet xml qui contient les attributs title, path, et url correspondants au noeud
xml est modifie dans cette fonction
*/
bkmNode.prototype.setAllAttributes = function(xml) {
this.setAttribute('title' , xml);
this.setAttribute('url', xml);
this.setAttribute('path', xml);
return xml;
}
/*
@ param node : un noeud de l'arborescence
@ param xml : un objet xml
@ param attribute : une chaine representant l'attribut a traiter (path, url ou title)
@ return : l'objet xml qui contient l'attribut correspondant au noeud
xml est modifie dans cette fonction
*/
bkmNode.prototype.setAttribute = function (attribute, xml) {
switch (attribute) {
case ('title') :
if (this.getName()) {
// on met un marqueur sur les live-marks pour pouvoir les reconnaitre lors d'un import
xml.@title = this.getName() + ((this.isLive()) ? LIVETAG : '');
}
break;
case ('path') :
xml.@path = this.getPath().toServer;
break;
case ('url') :
if (this.getUrl()) {
xml.@url = this.getUrl();
}
break;
}
return xml;
}
////////////////////////////////////
//////////////////////////////////////
// privateMgr
// :: manage private bookmarks list in %profile%/$YOONO_DIR/$PRIVATEFILE
// :: Display locks in front of private folders
// :: Call server to synchronise
var privateMgr = {
file : DIRSERVICE.get("ProfDS", CI.nsIFile),
init : function () {
try {
// Releases before 3.0.6 wrote the locked folders file in the user chrome directory
// Newer releases write the locked file in the yoono directory
// If the file exists at the old location, move it first
var oldFile = DIRSERVICE.get("UChrm", CI.nsIFile);
oldFile.append(PRIVATEFILE);
if (oldFile.exists()) {
var newFile = DIRSERVICE.get('ProfDS', CI.nsIFile);
newFile.append(YOONO_DIR);
oldFile.moveTo(newFile,PRIVATEFILE);
}
this.file.append(YOONO_DIR);
this.file.append(PRIVATEFILE);
this.readList();
} catch(e) {
log.exception(e);
}
},
uninstall : function () {
try {
var file = DIRSERVICE.get("ProfDS", CI.nsIFile);
file.append(YOONO_DIR);
file.append(PRIVATEFILE);
if( file.exists() ) {
file.remove(true);
}
} catch(e) {}
},
appendToList : function (newPrivateFolders) {
try {
var outputStream = CL["@mozilla.org/network/file-output-stream;1"].createInstance(CI.nsIFileOutputStream);
// 0x02 | 0x10 : WRONLY, APPEND or CREATE
outputStream.init(this.file, 0x02 | 0x08 | 0x10 , 0644, 0);
for (var id in newPrivateFolders) {
outputStream.write(id + '\n' , id.length + 1);
}
outputStream.close();
} catch(e) {
log.exception(e);
}
},
addToList : function (id) {
var node = gBkmById[id];
if(!node)
return log.error('NULL : ' + id);
if (node.isPrivate())
return log.debug("node is already private");
if (node.isFolder()) {
node.traverseNode({onNode:function(child) {
if (!child.isFolder())
yoono.server.launch('remove-link', child);
}});
node.setPrivate(true);
yoono.server.launch('add-private-folder', node);
} else {
log.error("add bkm to private list ? (must be called only on folders)");
}
this.saveList();
log.info(node.getName() + ' becomes private');
},
removeFromList : function (id) {
var node = gBkmById[id];
if(!node)
return log.error('NULL : ' + id) ;
if (!node.isPrivate())
return log.error("node is not private : "+id);
node.setPrivate(false);
log.info(node.getName() + ' set public by user');
if (node.isFolder()) {
yoono.server.launch('remove-private-folder', node);
node.traverseNode({onNode:function(child) {
if (!child.isFolder()) {
yoono.server.launch('add-link', child);
}
}});
} else {
log.error("remove bkm from private list ? (must be called only on folders)");
}
this.saveList();
},
/* Called on a private folder that the server says is now public
* folder must be removed from private folders list
*/
removeFolderFromList : function (id) {
var node = gBkmById[id];
node.setPrivate(false);
log.info(node.getName() + ' set public by server');
this.saveList();
node.resetSyncid();
},
// cette fonction doit etre appelee une fois que les marque-pages ont ete charges
readList : function() {
if (!this.file.exists()) {
return;
}
var istream = CL["@mozilla.org/network/file-input-stream;1"].createInstance(CI.nsIFileInputStream);
istream.init(this.file, 0x01, 0444, 0);
istream.QueryInterface(CI.nsILineInputStream);
var _uptodate = true;
var line = {};
var hasmorelines;
do {
hasmorelines = istream.readLine(line);
// on elimine les lignes vides, sources d'erreurs
if (line.value) {
// on ne garde que si la ressource existe reelement
if (gBkmById[line.value]) {
gBkmById[line.value].setPrivate(true);
// sinon, on met le fichier a jour
} else {
_uptodate = false;
}
}
} while(hasmorelines);
istream.close();
if (!_uptodate) {
this.saveList();
}
},
clearList : function() {
log.debug('Clearing private folders file');
var outputStream = CL["@mozilla.org/network/file-output-stream;1"].createInstance(CI.nsIFileOutputStream);
outputStream.init(this.file, 0x02 | 0x08 | 0x20 , 0644, 0);
outputStream.close();
},
saveList : function () {
var outputStream = CL["@mozilla.org/network/file-output-stream;1"].createInstance(CI.nsIFileOutputStream);
outputStream.init(this.file, 0x02 | 0x08 | 0x20 , 0644, 0);
for (var id in gBkmById) {
if (gBkmById[id].isPrivate()) {
outputStream.write(id + '\n' , id.length + 1);
}
}
outputStream.close();
}
}
////////////////////////////////////
var YOONO_BKM = new Bookmarks();